1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.google.common.util.concurrent;
18
19 import static com.google.common.base.Preconditions.checkNotNull;
20 import static junit.framework.Assert.assertEquals;
21 import static junit.framework.Assert.assertNotNull;
22 import static junit.framework.Assert.assertNull;
23 import static junit.framework.Assert.assertSame;
24
25 import com.google.common.testing.TearDown;
26
27 import junit.framework.AssertionFailedError;
28
29 import java.lang.reflect.InvocationTargetException;
30 import java.lang.reflect.Method;
31 import java.util.concurrent.SynchronousQueue;
32 import java.util.concurrent.TimeUnit;
33 import java.util.concurrent.TimeoutException;
34
35 import javax.annotation.Nullable;
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54 public final class TestThread<L> extends Thread implements TearDown {
55
56 private static final long DUE_DILIGENCE_MILLIS = 50;
57 private static final long TIMEOUT_MILLIS = 5000;
58
59 private final L lockLikeObject;
60
61 private final SynchronousQueue<Request> requestQueue = new SynchronousQueue<Request>();
62 private final SynchronousQueue<Response> responseQueue = new SynchronousQueue<Response>();
63
64 private Throwable uncaughtThrowable = null;
65
66 public TestThread(L lockLikeObject, String threadName) {
67 super(threadName);
68 this.lockLikeObject = checkNotNull(lockLikeObject);
69 start();
70 }
71
72
73
74
75
76 @SuppressWarnings("deprecation")
77 @Override public void tearDown() throws Exception {
78 stop();
79 join();
80
81 if (uncaughtThrowable != null) {
82 throw (AssertionFailedError) new AssertionFailedError("Uncaught throwable in " + getName())
83 .initCause(uncaughtThrowable);
84 }
85 }
86
87
88
89
90 public void callAndAssertReturns(String methodName, Object... arguments) throws Exception {
91 checkNotNull(methodName);
92 checkNotNull(arguments);
93 sendRequest(methodName, arguments);
94 assertSame(null, getResponse(methodName).getResult());
95 }
96
97
98
99
100
101 public void callAndAssertReturns(boolean expected, String methodName, Object... arguments)
102 throws Exception {
103 checkNotNull(methodName);
104 checkNotNull(arguments);
105 sendRequest(methodName, arguments);
106 assertEquals(expected, getResponse(methodName).getResult());
107 }
108
109
110
111
112
113 public void callAndAssertReturns(int expected, String methodName, Object... arguments)
114 throws Exception {
115 checkNotNull(methodName);
116 checkNotNull(arguments);
117 sendRequest(methodName, arguments);
118 assertEquals(expected, getResponse(methodName).getResult());
119 }
120
121
122
123
124
125 public void callAndAssertThrows(Class<? extends Throwable> expected,
126 String methodName, Object... arguments) throws Exception {
127 checkNotNull(expected);
128 checkNotNull(methodName);
129 checkNotNull(arguments);
130 sendRequest(methodName, arguments);
131 assertEquals(expected, getResponse(methodName).getThrowable().getClass());
132 }
133
134
135
136
137
138
139 public void callAndAssertBlocks(String methodName, Object... arguments) throws Exception {
140 checkNotNull(methodName);
141 checkNotNull(arguments);
142 assertEquals(false, invokeMethod("hasQueuedThread", this));
143 sendRequest(methodName, arguments);
144 Thread.sleep(DUE_DILIGENCE_MILLIS);
145 assertEquals(true, invokeMethod("hasQueuedThread", this));
146 assertNull(responseQueue.poll());
147 }
148
149
150
151
152
153
154
155
156 public void callAndAssertWaits(String methodName, Object conditionLikeObject)
157 throws Exception {
158 checkNotNull(methodName);
159 checkNotNull(conditionLikeObject);
160
161
162 sendRequest(methodName, conditionLikeObject);
163 Thread.sleep(DUE_DILIGENCE_MILLIS);
164 assertEquals(true, invokeMethod("hasWaiters", conditionLikeObject));
165 assertNull(responseQueue.poll());
166 }
167
168
169
170
171
172 public void assertPriorCallReturns(@Nullable String methodName) throws Exception {
173 assertEquals(null, getResponse(methodName).getResult());
174 }
175
176
177
178
179
180 public void assertPriorCallReturns(boolean expected, @Nullable String methodName)
181 throws Exception {
182 assertEquals(expected, getResponse(methodName).getResult());
183 }
184
185
186
187
188
189
190
191 private void sendRequest(String methodName, Object... arguments) throws Exception {
192 if (!requestQueue.offer(
193 new Request(methodName, arguments), TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)) {
194 throw new TimeoutException();
195 }
196 }
197
198
199
200
201
202
203
204
205
206 private Response getResponse(String methodName) throws Exception {
207 Response response = responseQueue.poll(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
208 if (response == null) {
209 throw new TimeoutException();
210 }
211 assertEquals(methodName, response.methodName);
212 return response;
213 }
214
215 private Object invokeMethod(String methodName, Object... arguments) throws Exception {
216 return getMethod(methodName, arguments).invoke(lockLikeObject, arguments);
217 }
218
219 private Method getMethod(String methodName, Object... arguments) throws Exception {
220 METHODS: for (Method method : lockLikeObject.getClass().getMethods()) {
221 Class<?>[] parameterTypes = method.getParameterTypes();
222 if (method.getName().equals(methodName) && (parameterTypes.length == arguments.length)) {
223 for (int i = 0; i < arguments.length; i++) {
224 if (!parameterTypes[i].isAssignableFrom(arguments[i].getClass())) {
225 continue METHODS;
226 }
227 }
228 return method;
229 }
230 }
231 throw new NoSuchMethodError(methodName);
232 }
233
234 @Override public void run() {
235 assertSame(this, Thread.currentThread());
236 try {
237 while (true) {
238 Request request = requestQueue.take();
239 Object result;
240 try {
241 result = invokeMethod(request.methodName, request.arguments);
242 } catch (ThreadDeath death) {
243 return;
244 } catch (InvocationTargetException exception) {
245 responseQueue.put(
246 new Response(request.methodName, null, exception.getTargetException()));
247 continue;
248 } catch (Throwable throwable) {
249 responseQueue.put(new Response(request.methodName, null, throwable));
250 continue;
251 }
252 responseQueue.put(new Response(request.methodName, result, null));
253 }
254 } catch (ThreadDeath death) {
255 return;
256 } catch (InterruptedException ignored) {
257
258 } catch (Throwable uncaught) {
259 this.uncaughtThrowable = uncaught;
260 }
261 }
262
263 private static class Request {
264 final String methodName;
265 final Object[] arguments;
266
267 Request(String methodName, Object[] arguments) {
268 this.methodName = checkNotNull(methodName);
269 this.arguments = checkNotNull(arguments);
270 }
271 }
272
273 private static class Response {
274 final String methodName;
275 final Object result;
276 final Throwable throwable;
277
278 Response(String methodName, Object result, Throwable throwable) {
279 this.methodName = methodName;
280 this.result = result;
281 this.throwable = throwable;
282 }
283
284 Object getResult() {
285 if (throwable != null) {
286 throw (AssertionFailedError) new AssertionFailedError().initCause(throwable);
287 }
288 return result;
289 }
290
291 Throwable getThrowable() {
292 assertNotNull(throwable);
293 return throwable;
294 }
295 }
296 }